//-----------------------------------------------------------------------------
// Filename: SwitchCallApp.cs
//
// Description: A two legged call that allows different credentials to be used to an upstream
// UAS.
// 
// History:
// 22 Nov 2006	    Aaron Clauson	    Created.
// 24 April 2007    Guillaume Bonnet    Add switchboard related functions.
// 17 Apr 2008      Aaron Clauson       Added tracing.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2008 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using BlueFace.VoIP.Authentication;
using BlueFace.VoIP.Net;
using BlueFace.VoIP.Net.App;
using BlueFace.VoIP.Net.SIP;
using BlueFace.VoIP.SIPServer;
using BlueFace.VoIP.SIPServerCores.StatefulProxy;
using Heijden.DNS;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.SIPServerCores.StatefulProxy.DialPlanApps
{
    public delegate void SIPSwitchCallAnsweredDelegate(SwitchCallApp sipSwitchCall, SIPResponse answeredResponse);
    public delegate void SIPSwitchCallFailedDelegate(SwitchCallApp sipSwitchCall, string errorMessage);
    public delegate void SIPSwitchCallRingingDelegate(SwitchCallApp sipSwitchCall, SIPResponse ringingResponse);
    
    public class SwitchCallApp
    {
        private const int DNS_LOOKUP_TIMEOUT = 5000;
        private const char CUSTOM_HEADERS_SEPARATOR = '|';

        private static ILog logger = AppState.GetLogger("sipproxy");
        private static string m_userAgent = StatefulProxyCore.SIPPROXY_USERAGENT;
        private static string m_transportParam = SIPHeaderAncillary.SIP_HEADERANC_TRANSPORT;

        private event ProxyLogDelegate m_statefulProxyLogEvent;

        private SIPTransport m_sipTransport;

        private string m_localContactSocket;                                    // Host address used in the Contact header for requests generated by this proxy. In dialogue requests from the remote agent will use this address.
        public string Owner { get; private set; }                               // If the UAC is authenticated holds the username of the client.
        public SIPCallDescriptor ForwardedCallStruct { get; private set; }      // Describes the server leg of the call from the sipswitch.
        public UASInviteTransaction ClientTransaction { get; private set; }     // Proxy transaction established with a client making a call out through the switch.
        public UACInviteTransaction ForwardedTransaction { get; private set; }  // Proxy transaction established with the third party server for an outbound switch call leg.
        public IPEndPoint ForwardedEndPoint { get; private set; }               // The destination socket that the forwarded leg of the call was sent to.
        
        private bool m_callCancelled = false;                       // It's possible for the call to be cancelled before the INVITE has been sent. This could occur if a DNS lookup on the server takes a while.
        private bool m_hungupOnCancel = false;                      // Set to true if a call has been cancelled AND and then an Ok response was received AND a BYE has been sent to hang it up. This variable is used to stop another BYE transaction being generated.
        private int m_serverAuthAttempts;                           // Used to determine if credentials for a server leg call fail.
        private SIPNonInviteTransaction m_cancelTransaction;        // If the server call is cancelled this transaction contains the CANCEL in case it needs to be resent.
        private IPEndPoint m_outboundProxySocket;                   // If the application server should forward all calls through an outbound proxy this object will be set, otherwise calls should be sent directly.
        private IPAddress m_resolvedCallAddress;                    // If the call descriptor host gets resolved the IP address will be stored here.

        public event SIPSwitchCallAnsweredDelegate CallAnswered;
        public event SIPSwitchCallFailedDelegate CallFailed;
        public event SIPSwitchCallRingingDelegate CallRinging;

        public static Int64 Created;
        public static Int64 Destroyed;

        public SwitchCallApp(
            SIPTransport sipTransport,
            ProxyLogDelegate statefulProxyLogEvent,
            string localContactSocket,
            UASInviteTransaction clientTransaction,
            string username,
            SIPCallDescriptor call,
            IPEndPoint outboundProxySocket)
        {
            m_sipTransport = sipTransport;
            m_statefulProxyLogEvent = statefulProxyLogEvent;
            m_localContactSocket = localContactSocket;
            ClientTransaction = clientTransaction;
            Owner = username;
            ForwardedCallStruct = call;
            m_outboundProxySocket = outboundProxySocket;
        }

        public void Start(object state)
        {
            try
            {
                Created++;
                SIPRequest sipRequest = ClientTransaction.TransactionRequest;

                if (!m_callCancelled)
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "New SwitchCall starting for " + sipRequest.URI.ToString() + ", attempting to resolve " + ForwardedCallStruct.Host + ".", Owner));

                    DNSResponse uriDNSEntry = DNSManager.Lookup(IPSocket.ParseHostFromSocket(ForwardedCallStruct.Host), DNSQType.A, DNS_LOOKUP_TIMEOUT, null);

                    if (uriDNSEntry != null && uriDNSEntry.RecordsA != null && uriDNSEntry.RecordsA.Length > 0)
                    {
                        m_resolvedCallAddress = uriDNSEntry.RecordsA[0].Address;

                        if (!m_callCancelled)
                        {
                            SIPRequest switchServerInvite = GetInviteRequest(ForwardedCallStruct, sipRequest.Body, CallProperties.CreateBranchId(), CallProperties.CreateNewCallId(), m_localContactSocket);
                            
                            // Create a new UAC transaction for forwarded leg of the call
                            ForwardedTransaction = m_sipTransport.CreateUACTransaction(switchServerInvite, ForwardedEndPoint, m_sipTransport.GetTransportContact(null), switchServerInvite.URI.Protocol);
                            ForwardedTransaction.CDR.Owner = Owner;
                            ForwardedTransaction.UACInviteTransactionInformationResponseReceived += new SIPTransactionResponseReceivedDelegate(SwitchServerInformationResponseReceived);
                            ForwardedTransaction.UACInviteTransactionFinalResponseReceived += new SIPTransactionResponseReceivedDelegate(SwitchServerFinalResponseReceived);
                            ForwardedTransaction.UACInviteTransactionTimedOut += new SIPTransactionTimedOutDelegate(SwitchServerTimedOut);
                            ForwardedTransaction.TransactionTraceMessage += new SIPTransactionTraceMessageDelegate(TransactionTraceMessage);

                            ForwardedEndPoint = SetRequestRoute(ForwardedCallStruct, switchServerInvite, m_resolvedCallAddress);
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Switching " + sipRequest.URI.CanonicalAddress + "->" + SIPURI.ParseSIPURI(ForwardedCallStruct.Uri).CanonicalAddress + " via " + ForwardedEndPoint + ".", Owner));
                            ForwardedTransaction.SendInviteRequest(ForwardedEndPoint, ForwardedTransaction.TransactionRequest);
                        }
                    }
                    else
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Forward leg failed, could not resolve " + IPSocket.ParseHostFromSocket(ForwardedCallStruct.Host), Owner));
                        //logger.Warn("SIPCallDescriptor Could not resolve " + IPSocket.ParseHostFromSocket(ForwardedCallStruct.Host) + ".");
                        CallFailed(this, "Unresolvable host " + IPSocket.ParseHostFromSocket(ForwardedCallStruct.Host));
                    }
                }
            }
            catch (Exception excp)
            {
                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Exception StartNewCall. " + excp.Message, Owner));
                //logger.Error("Exception StartNewCall. " + excp.Message);
                CallFailed(this, excp.Message);
            }
        }

        private void SwitchServerTimedOut(SIPTransaction sipTransaction)
        {
            CallFailed(this, "Timeout, no response from server");
        }

        private void SwitchServerFinalResponseReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            try
            {
                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Response " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + " for " + ForwardedTransaction.TransactionRequest.URI.ToString() + ".", Owner));
                //m_sipTrace += "Received " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss") + " " + localEndPoint + "<-" + remoteEndPoint + "\r\n" + sipResponse.ToString();

                ForwardedTransaction.UACInviteTransactionInformationResponseReceived -= new SIPTransactionResponseReceivedDelegate(SwitchServerInformationResponseReceived);
                ForwardedTransaction.UACInviteTransactionFinalResponseReceived -= new SIPTransactionResponseReceivedDelegate(SwitchServerFinalResponseReceived);
                ForwardedTransaction.TransactionTraceMessage -= new SIPTransactionTraceMessageDelegate(TransactionTraceMessage);

                if (m_callCancelled && sipResponse.Status == SIPResponseStatusCodesEnum.RequestTerminated)
                {
                    // No action required. Correctly received request terminated on an INVITE we cancelled.
                }
                else if (m_callCancelled)
                {
                    if (m_hungupOnCancel)
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "A cancelled call to " + ForwardedCallStruct.Uri.ToString() + " has been answered AND has already been hungup, no further action being taken.", Owner));
                    }
                    else
                    {
                        m_hungupOnCancel = true;

                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "A cancelled call to " + ForwardedCallStruct.Uri.ToString() + " has been answered, hanging up.", Owner));

                        if (sipResponse.Header.Contact != null && sipResponse.Header.Contact.Count > 0)
                        {
                            SIPURI byeURI = sipResponse.Header.Contact[0].ContactURI;
                            SIPRequest byeRequest = GetByeRequest(sipResponse, byeURI, localEndPoint);
                            SIPNonInviteTransaction byeTransaction = m_sipTransport.CreateNonInviteTransaction(byeRequest, byeRequest.GetRequestEndPoint(), localEndPoint, sipTransaction.Protocol);
                            byeTransaction.SendReliableRequest();
                        }
                        else
                        {
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "No contact header provided on response for cancelled call to " + ForwardedCallStruct.Uri.ToString() + " no further action.", Owner));
                        }
                    }
                }
                else if (sipResponse.Status == SIPResponseStatusCodesEnum.ProxyAuthenticationRequired || sipResponse.Status == SIPResponseStatusCodesEnum.Unauthorised)
                {
                    logger.Debug("AuthReqd Final response " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + " for " + ForwardedTransaction.TransactionRequest.URI.ToString() + ".");

                    #region Authenticate client call to third party server.

                    if (!m_callCancelled)
                    {
                        if (ForwardedCallStruct.Password == null || ForwardedCallStruct.Password.Trim().Length == 0)
                        {
                            // No point trying to authenticate if there is no password to use.
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Forward leg failed, authentication was requested but no credentials were available.", Owner));
                            CallFailed(this, "Authentication requested when no credentials available");
                        }
                        else if (m_serverAuthAttempts == 0)
                        {
                            m_serverAuthAttempts = 1;

                            // Resend INVITE with credentials.
                            string nonce = null;
                            string realm = null;

                            if (sipResponse.Header.AuthenticationHeader != null)
                            {
                                nonce = sipResponse.Header.AuthenticationHeader.AuthRequest.Nonce;
                                realm = sipResponse.Header.AuthenticationHeader.AuthRequest.Realm;
                            }
                            else
                            {
                                nonce = Crypto.GetRandomString(12);
                                realm = sipTransaction.TransactionRequest.URI.Host;
                            }

                            AuthorizationRequest authRequest = new AuthorizationRequest(realm, ForwardedCallStruct.Username, ForwardedCallStruct.Password, ForwardedCallStruct.Uri.ToString(), nonce, SIPMethodsEnum.INVITE.ToString());
                            SIPRequest authInviteRequest = ForwardedTransaction.TransactionRequest;
                            authInviteRequest.Header.Via.TopViaHeader.Branch = CallProperties.CreateBranchId();
                            authInviteRequest.Header.CSeq = authInviteRequest.Header.CSeq + 1;

                            if (sipResponse.Header.WWWAuthenticate)
                            {
                                authInviteRequest.Header.Authorize = true;
                            }
                            else if (sipResponse.Header.ProxyAuthenticate)
                            {
                                authInviteRequest.Header.ProxyAuthorization = true;
                            }
                            else if (sipResponse.Header.Authorize)
                            {
                                authInviteRequest.Header.Authorize = true;
                            }
                            else
                            {
                                authInviteRequest.Header.ProxyAuthorization = true;
                            }

                            authInviteRequest.Header.AuthenticationHeader = new SIPAuthenticationHeader(authRequest);
                            authInviteRequest.Header.AuthenticationHeader.AuthRequest.Response = authRequest.Digest;

                            // Create a new UAC transaction to establish the authenticated server call.
                            ForwardedTransaction = m_sipTransport.CreateUACTransaction(authInviteRequest, SIPURI.GetURIEndPoint(ForwardedCallStruct.Host), m_sipTransport.GetTransportContact(null), SIPProtocolsEnum.UDP);
                            ForwardedTransaction.CDR.Owner = Owner;
                            ForwardedTransaction.UACInviteTransactionInformationResponseReceived += new SIPTransactionResponseReceivedDelegate(SwitchServerInformationResponseReceived);
                            ForwardedTransaction.UACInviteTransactionFinalResponseReceived += new SIPTransactionResponseReceivedDelegate(SwitchServerFinalResponseReceived);
                            ForwardedTransaction.UACInviteTransactionTimedOut += new SIPTransactionTimedOutDelegate(SwitchServerTimedOut);
                            ForwardedTransaction.TransactionTraceMessage += new SIPTransactionTraceMessageDelegate(TransactionTraceMessage);

                            ForwardedEndPoint = SetRequestRoute(ForwardedCallStruct, authInviteRequest, m_resolvedCallAddress);

                            //logger.Debug("Sending authenticated switchcall INVITE to " + ForwardedCallStruct.Host + ".");
                            ForwardedTransaction.SendInviteRequest(ForwardedEndPoint, authInviteRequest);
                            //m_sipTrace += "Sending " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss") + " " + localEndPoint + "->" + ForwardedTransaction.TransactionRequest.GetRequestEndPoint() + "\r\n" + ForwardedTransaction.TransactionRequest.ToString();
                        }
                        else
                        {
                            //logger.Debug("Authentication of client call to switch server failed.");
                            CallFailed(this, "Authentication with provided credentials failed");
                        }
                    }

                    #endregion
                }
                else
                {
                    //m_callInProgress = false; // the call is now established
                    //logger.Debug("Final response " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + " for " + ForwardedTransaction.TransactionRequest.URI.ToString() + ".");
                    CallAnswered(this, sipResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SwitchServerFinalResponseReceived. " + excp.Message);
            }
        }

        private void SwitchServerInformationResponseReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            logger.Debug("Information response " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + " for " + ForwardedTransaction.TransactionRequest.URI.ToString() + ".");
            CallRinging(this, sipResponse);
        }

        public void CancelServerCall()
        {
            try
            {
                m_callCancelled = true;

                // Cancel server call.
                if (ForwardedTransaction == null)
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Cancelling forwarded call leg " + ForwardedCallStruct.Uri.ToString() + ", server transaction has not been created yet no CANCEL request required.", Owner));
                }
                else if (m_cancelTransaction != null)
                {
                    if (m_cancelTransaction.TransactionState != SIPTransactionStatesEnum.Completed)
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Call " + ForwardedTransaction.TransactionRequest.URI.ToString() + " has already been cancelled once, trying again.", Owner));
                        m_cancelTransaction.SendRequest(m_cancelTransaction.TransactionRequest);
                    }
                    else
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan,"Call " + ForwardedTransaction.TransactionRequest.URI.ToString() + " has already responded to CANCEL, probably overlap in messages not re-sending.", Owner));
                    }
                }
                else if (ForwardedTransaction.TransactionState == SIPTransactionStatesEnum.Proceeding || ForwardedTransaction.TransactionState == SIPTransactionStatesEnum.Trying)
                {
                    //logger.Debug("Cancelling forwarded call leg, sending CANCEL to " + ForwardedTransaction.TransactionRequest.URI.ToString() + " (transid: " + ForwardedTransaction.TransactionId + ").");
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Cancelling forwarded call leg, sending CANCEL to " + ForwardedTransaction.TransactionRequest.URI.ToString() + ".", Owner));

                    // No reponse has been received from the server so no CANCEL request neccessary, stop any retransmits of the INVITE.
                    ForwardedTransaction.CancelCall();

                    SIPRequest cancelRequest = GetCancelRequest(ForwardedTransaction.TransactionRequest);
                    SetRequestRoute(ForwardedCallStruct, cancelRequest, m_resolvedCallAddress);
                    m_cancelTransaction = m_sipTransport.CreateNonInviteTransaction(cancelRequest, ForwardedTransaction.RemoteEndPoint, ForwardedTransaction.SendFromEndPoint, SIPProtocolsEnum.UDP);
                    m_cancelTransaction.SendRequest(cancelRequest);
                }
                else 
                {
                    // No reponse has been received from the server so no CANCEL request neccessary, stop any retransmits of the INVITE.
                    ForwardedTransaction.CancelCall();

                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Cancelling forwarded call leg " + ForwardedCallStruct.Uri.ToString() + ", no response from server has been received so no CANCEL request required.", Owner));
                }
            }
            catch (Exception excp)
            {
                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Exception CancelServerCall. " + excp.Message, Owner));
            }
        }

        private SIPRequest GetInviteRequest(SIPCallDescriptor switchCallStruct, string inviteBody, string branchId, string callId, string proxyContactHost)
        {
            return GetInviteRequest(switchCallStruct, m_sipTransport.GetDefaultTransportContact(SIPURI.ParseSIPURI(switchCallStruct.Uri).Protocol), inviteBody, branchId, callId, proxyContactHost);
        }

        private SIPRequest GetInviteRequest(SIPCallDescriptor switchCallStruct, IPEndPoint localEndPoint, string inviteBody, string branchId, string callId, string localSIPSocket)
        {
            SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, switchCallStruct.Uri);
            SIPProtocolsEnum protocol = inviteRequest.URI.Protocol;

            SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader(switchCallStruct.From), SIPToHeader.ParseToHeader(switchCallStruct.To), 1, callId);

            inviteHeader.From.FromTag = CallProperties.CreateNewTag();

            // For incoming calls forwarded via the dial plan the username needs to go into the Contact header.
            string contactHost = (localSIPSocket != null) ? localSIPSocket : IPSocket.GetSocketString(localEndPoint);
            if (switchCallStruct.Username != null && switchCallStruct.Username.Trim().Length > 0)
            {
                inviteHeader.Contact = SIPContactHeader.ParseContactHeader("sip:" + switchCallStruct.Username + "@" + contactHost);
            }
            else
            {
                inviteHeader.Contact = SIPContactHeader.ParseContactHeader("sip:" + contactHost);
            }

            if (protocol == SIPProtocolsEnum.TCP)
            {
                inviteHeader.Contact[0].ContactURI.Parameters.Set(m_transportParam, "TCP");
            }

            inviteHeader.CSeqMethod = SIPMethodsEnum.INVITE;
            inviteHeader.UserAgent = m_userAgent;
            inviteRequest.Header = inviteHeader;

            SIPViaHeader viaHeader = new SIPViaHeader(localEndPoint.Address.ToString(), localEndPoint.Port, branchId, protocol);
            inviteRequest.Header.Via.PushViaHeader(viaHeader);
            
            inviteRequest.Body = inviteBody;
            inviteRequest.Header.ContentLength = inviteBody.Length;
            inviteRequest.Header.ContentType = "application/sdp";

            try
            {
                if (switchCallStruct.CustomHeaders != null && switchCallStruct.CustomHeaders.Trim().Length > 0)
                {
                    string[] customerHeadersList = switchCallStruct.CustomHeaders.Split(CUSTOM_HEADERS_SEPARATOR);

                    if (customerHeadersList != null && customerHeadersList.Length > 0)
                    {
                        foreach (string customHeader in customerHeadersList)
                        {
                            if (customHeader.IndexOf(':') == -1)
                            {
                                logger.Debug("Skipping custom header due to missing colon, " + customHeader + ".");
                                continue;
                            }
                            else
                            {
                                string headerName = customHeader.Substring(0, customHeader.IndexOf(':'));
                                if (headerName != null && Regex.Match(headerName.Trim(), "^(Via|From|To|Contact|CSeq|Call-ID|Max-Forwards|Content)$", RegexOptions.IgnoreCase).Success)
                                {
                                    logger.Debug("Skipping custom header due to an non-permitted string in header name, " + customHeader + ".");
                                    continue;
                                }
                                else
                                {
                                    if (headerName == SIPHeaders.SIP_HEADER_USERAGENT)
                                    {
                                        if (customHeader.Length > customHeader.IndexOf(':'))
                                        {
                                            inviteRequest.Header.UserAgent = customHeader.Substring(customHeader.IndexOf(':') + 1);
                                        }
                                    }
                                    else
                                    {
                                        inviteRequest.Header.UnknownHeaders.Add(customHeader.Trim());
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception Parsing CustomHeader for GetInviteRequest. " + excp.Message + switchCallStruct.CustomHeaders);
            }

            return inviteRequest;
        }

        private SIPRequest GetCancelRequest(SIPRequest originalRequest)
        {
            SIPRequest cancelRequest = new SIPRequest(SIPMethodsEnum.CANCEL, originalRequest.URI);

            SIPHeader originalHeader = originalRequest.Header;
            SIPHeader cancelHeader = new SIPHeader(originalHeader.From, originalHeader.To, originalHeader.CSeq, originalHeader.CallId);
            cancelRequest.Header = cancelHeader;
            cancelHeader.CSeqMethod = SIPMethodsEnum.CANCEL;

            cancelHeader.Via = originalHeader.Via;

            return cancelRequest;
        }

        private SIPRequest GetByeRequest(SIPResponse answeredResponse, SIPURI byeURI, IPEndPoint localEndPoint)
        {
            SIPRequest byeRequest = new SIPRequest(SIPMethodsEnum.BYE, byeURI);
            SIPFromHeader byeFromHeader = answeredResponse.Header.From;
            SIPToHeader byeToHeader = answeredResponse.Header.To;
            int cseq = answeredResponse.Header.CSeq + 1;

            SIPHeader byeHeader = new SIPHeader(byeFromHeader, byeToHeader, cseq, answeredResponse.Header.CallId);
            byeHeader.CSeqMethod = SIPMethodsEnum.BYE;
            byeRequest.Header = byeHeader;

            if (answeredResponse.Header.Routes != null && answeredResponse.Header.Routes.Length > 0)
            {
                byeRequest.Header.Routes = answeredResponse.Header.Routes;
            }

            SIPViaHeader viaHeader = new SIPViaHeader(localEndPoint.Address.ToString(), localEndPoint.Port, CallProperties.CreateBranchId());
            byeRequest.Header.Via.PushViaHeader(viaHeader);

            return byeRequest;
        }

        /// <summary>
        /// Determines the next one or two hops for a SIP request.
        /// </summary>
        /// <param name="switchCallStruct">The call descriptor describing the call being created.</param>
        /// <param name="request">The request that is being created.</param>
        /// <param name="callResolvedAddress">The IP address of the destination host from the call descriptor.</param>
        /// <returns>A socket indicating the socket this request should be forwarded to.</returns>
        private IPEndPoint SetRequestRoute(SIPCallDescriptor switchCallStruct, SIPRequest request, IPAddress resolvedAddress)
        {
            int port = IPSocket.ParsePortFromSocket(switchCallStruct.Host);
            IPEndPoint nextHopEndPoint = (port == 0) ? new IPEndPoint(resolvedAddress, SIPConstants.DEFAULT_SIP_PORT) : new IPEndPoint(resolvedAddress, port);

            // The rules for deciding the next socket the INVITE request should be sent to are:
            // 1. If call is incoming use the settings in the call struct,
            // 2. If the call is outgoing use the m_outboundProxySocket if it is set and use a Route header to let the Proxy know the second socket,
            // 3. If the call is outgoing and the m_outboundProxySocket use the settings in the call struct.
            if (switchCallStruct.CallDirection == SIPCallDirection.Out && m_outboundProxySocket != null)
            {
                request.Header.Routes.PushRoute(new SIPRoute("sip:" + nextHopEndPoint.ToString(), true));
                return m_outboundProxySocket;
            }
            else
            {
                return nextHopEndPoint;
            }
        }

        private void TransactionTraceMessage(SIPTransaction sipTransaction, string message)
        {
            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.SIPTransaction, message, Owner));
        }

        private void FireProxyLogEvent(ProxyMonitorEvent monitorEvent)
        {
            if (m_statefulProxyLogEvent != null)
            {
                try
                {
                    m_statefulProxyLogEvent(monitorEvent);
                }
                catch (Exception excp)
                {
                    logger.Error("Exception FireProxyLogEvent StatefulProxyCore. " + excp.Message);
                }
            }
        }

        ~SwitchCallApp()
        {
            Destroyed++;
        }
    }
}
